﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2021-2025 LiLong                                              **
**  This file is part of CommonUnits.                                           **
**                                                                              **
**  CommonUnits is free software: you can redistribute it and/or modify         **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  CommonUnits is distributed in the hope that it will be useful,              **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with CommonUnits.  If not, see <https://www.gnu.org/licenses/>.       **
**********************************************************************************/
#pragma once

#include "StandardUnit.h"

#include <exception>
#include <functional>

namespace CU
{
template<typename...>
class UnitLists;

template<>
struct UnitLists<>
{
    constexpr static size_t count = 0;
};

template<typename Unit1, typename... _Units>
struct UnitLists<Unit1, _Units...> : private UnitLists<_Units...>
{
    using Type                    = Unit1;
    constexpr static size_t count = sizeof...(_Units) + 1;
};

template<size_t, typename...>
struct UnitListElement;

template<typename T, typename... Us>
struct UnitListElement<0, UnitLists<T, Us...>>
{
    using Type = T;
};

template<size_t index, typename T, typename... Us>
struct UnitListElement<index, UnitLists<T, Us...>> : UnitListElement<index - 1, UnitLists<Us...>>
{
};

template<size_t index, typename _UnitLists>
using UnitListElementType = typename UnitListElement<index, _UnitLists>::Type;

template<size_t, typename...>
struct _UnitListVisitor;

template<>
struct _UnitListVisitor<0, UnitLists<>>
{
    static constexpr bool visit(const auto&) { return false; }
};

template<size_t index, typename T, typename... Us>
struct _UnitListVisitor<index, UnitLists<T, Us...>> : _UnitListVisitor<index - 1, UnitLists<Us...>>
{
    using StringView = std::basic_string_view<typename decltype(T::unit)::CharT>;
    static bool visit(const std::function<bool(StringView, std::intmax_t, std::intmax_t)>& func)
    {
        if (!func(StringView(T::unit.data(), T::unit.size()), T::Ratio::num, T::Ratio::den))
        {
            return _UnitListVisitor<index - 1, UnitLists<Us...>>::visit(func);
        }
        return true;
    }
};

template<typename _UnitLists>
inline bool visitUnitList(
    const std::function<bool(std::basic_string_view<typename decltype(_UnitLists::Type::unit)::CharT>, std::intmax_t, std::intmax_t)>& func)
{
    return _UnitListVisitor<_UnitLists::count, _UnitLists>::visit(func);
}

template<typename _Unit, typename _UnitLists>
inline auto toUnitHelper(double value, const std::string& dest) -> _Unit
{
    _Unit unit;
    auto ret = visitUnitList<_UnitLists>(
        [&](std::string_view u, std::intmax_t num, std::intmax_t den)
        {
            if (dest == u)
            {
                auto cast = [](double value, std::intmax_t num, std::intmax_t den)
                {
                    auto divideNum = _Unit::Ratio::den * num;
                    auto divideDen = _Unit::Ratio::num * den;
                    using _ToRep   = typename _Unit::Type;
                    using _CR      = std::common_type_t<_ToRep, double, intmax_t>;

                    bool numIsOne = divideNum == 1;
                    bool denIsOne = divideDen == 1;

                    if (denIsOne)
                    {
                        if (numIsOne)
                        {
                            return static_cast<_Unit>(static_cast<_ToRep>(value));
                        }
                        else
                        {
                            return static_cast<_Unit>(static_cast<_ToRep>(static_cast<_CR>(value) * static_cast<_CR>(divideNum)));
                        }
                    }
                    else
                    {
                        if (numIsOne)
                        {
                            return static_cast<_Unit>(static_cast<_ToRep>(static_cast<_CR>(value) / static_cast<_CR>(divideDen)));
                        }
                        else
                        {
                            return static_cast<_Unit>(
                                static_cast<_ToRep>(static_cast<_CR>(value) * static_cast<_CR>(divideNum) / static_cast<_CR>(divideDen)));
                        }
                    }
                };
                unit = cast(value, num, den);
                return true;
            }
            return false;
        });
    if (!ret)
        throw std::invalid_argument("Invalid unit " + dest);
    return unit;
}

template<int index, typename UnitLists>
inline auto adjustUnitHelper(const auto& unit)
{
    if constexpr (UnitLists::count == index)
        return std::make_pair<typename UnitListElementType<0, UnitLists>::Type, std::string>(unit.count(), unit.unit());
    else
    {
        using T = UnitListElementType<index, UnitLists>;
        auto u  = commonUnitsCast<T>(unit);
        if (std::abs(u.count()) < 1.0)
            return std::make_pair<typename T::Type, std::string>(unit.count(), unit.unit());
        else
            return adjustUnitHelper<index + 1, UnitLists>(u);
    }
}

template<typename... Args>
inline auto adjustUnit(const auto& unit)
{
    using UList = UnitLists<Args...>;
    return adjustUnitHelper<0, UList>(unit);
}
} // namespace CU
